/**
*   @file imageADT.c
*   Implementacion de un ADT para manejo de imagenes.
*   Fecha de ultima modificacion 06/11/2007.
*/

#include "../imageADT/imageADT.h"
#include "../quadTreeADT/quadTreeADT.h"
#include "../hashADT/hash.h"

/**
*   \var _BM_
*   Valor del unsigned short que representa BM.
*/
#define _BM_ 19778

/**
*	\struct BITMAPFILEHEADER
* 	Primer header que contiene informacion acerca de la imagen.
* 	@param type Valor que tiene que ser igual a _BM_.
* 	@param size tamanio de la imagen.
* 	@param reserved1 Espacio reservado.
* 	@param reserved2 Espacio reservado.
* 	@param offsetbits Offset donde inicia la imagen.
*/ 
typedef struct{
	unsigned short type;
	unsigned long size;
	unsigned short reserved1;
	unsigned short reserved2;
	unsigned long offsetbits;
}BITMAPFILEHEADER;

/**
*	\struct BITMAPINFOHEADER
* 	Segundo header que contiene informacion acerca de la imagen.
* 	@param size Tamanio del header.
* 	@param width Ancho de la imagen.
*	@param height Altura de la imagen.
* 	@param planes Planos de la imagen.
* 	@param bitcount Profundidad de la imagen.
* 	@param compression Compresion de la imagen.
* 	@param sizeimage Tamanio de la imagen.
* 	@param xpelsppermeter Dato dentro del header del bmp que no se usa en el programa.
* 	@param ypelsppermeter Dato dentro del header del bmp que no se usa en el programa.
*	@param colorused Dato dentro del header del bmp que no se usa en el programa.
* 	@param colorsimportant Dato dentro del header del bmp que no se usa en el programa. 
*/ 
typedef struct{
	unsigned long size;
	unsigned long width;
	unsigned long height;
	unsigned short planes;
	unsigned short bitcount;
	unsigned long compression;
	unsigned long sizeimage;
	long xpelspermeter;
	long ypelspermeter;
	unsigned long colorsused;
	unsigned long colorsimportant;
}BITMAPINFOHEADER;

/**
*	\struct SINGLE_PIXEL
* 	Estructura correspondiente a un pixel. Contiene el RGB.
* 	@param blue Valor del azul dentro del RGB.
* 	@param green Valor del verde dentro del RGB.
* 	@param red Valor del rojo dentro del RGB. 
*/ 
typedef struct{
	unsigned char blue;
	unsigned char green;
	unsigned char red;
}SINGLE_PIXEL;

/**
*	\struct imageCDT
* 	Estructura del header de la imagen.
* 	@param header Puntero a la estructura de tipo BITMAPFILEHEADER.
* 	@param info Puntero a la estructura de tipo BITMAPINFOHEADER.
* 	@param qTree Puntero al arbol.
* 	@param sizeFixed Entero del tamanio del cuadrado 2^n.
* 	@param n Entero por el cual se eleva dos para obtener sizeFixed.
*/ 
struct imageCDT
{
	BITMAPFILEHEADER * header;
	BITMAPINFOHEADER * info;
	quadADT qTree;
	unsigned long sizeFixed;
	int n;
};

static int readFirstHeader(imageADT image, FILE * source);
static int writeFirstHeader(imageADT image, FILE * dest);
static int readSecondHeader(imageADT image, FILE * source);
static int writeSecondHeader(imageADT image, FILE * dest);
static void fixSize(imageADT image);
static unsigned long isPower2(unsigned long x);
static unsigned long seekPower2(unsigned long x);
static int seekN(int x);
static int createQTree(imageADT image, FILE * source);
static double toBin( int num );
static int calcQcode( char * code, int fil, int col, int n );
static void writePixel(SINGLE_PIXEL * pixel, FILE * dest);
static int writeImage(imageADT image, FILE * dest);
static void freePixel(ElementT pixel);
static int copyPixel(ElementT pixDest, ElementT pixSource);
static int cmpElement(ElementT color1, ElementT color2);
static int seekMod4(unsigned long var);
static int hash(void * element, int interval);

imageADT openImage(char * file)
{
	imageADT image;
	FILE * source;


	if( (image = malloc(sizeof(struct imageCDT))) == NULL )
		return NULL;

	if( (source = fopen( file, "r")) == NULL )
	{
		free(image);
		return NULL;
	}
	if( !readFirstHeader(image, source) )
	{
		free(image);
		return NULL;
	} 

	if( !readSecondHeader(image, source) )
	{
		free(image);
		return NULL;
	}

	fixSize(image); /*Arregla el tamanio*/
	
	if( (image->n = seekN( image->sizeFixed)) == 0 ) /*Se busca la profundidad del arbol*/
		return NULL;

	if( !createQTree(image, source) )
	{
		free(image);
		return NULL;
	}

	fclose(source);

	return image;
}

/*
 * createQTree va a crear el QuadTree de la imagen del archivo.
 * Se halla el n de la imagen que sirve para calcular el qCode.
 * Se cicla por la imagen de tamanio sizeFixed y se manda el pixel
 * a insertQNode.
 * Una vez insertado el pixel en el arbol, se libera el qCode.
 */
static int createQTree(imageADT image, FILE * source)
{
	SINGLE_PIXEL * pixelVec;
	int i, j;
	char * qCode;
	int mod4;

	mod4 = seekMod4(image->info->width * 3);


	if( (image->qTree = NewQuad(sizeof(SINGLE_PIXEL), freePixel, copyPixel, cmpElement)) == NULL)
		return 0;

	if( (pixelVec = malloc(image->info->width*3)) == NULL )
		return 0;

	if( (qCode = malloc(image->n + 1)) == NULL )
		return 0; 

	for( i=0 ; i < image->info->height ; i++ )
	{
		fread(pixelVec, sizeof(SINGLE_PIXEL), image->info->width, source);
		fseek(source, mod4, SEEK_CUR);

		for( j=0 ; j < image->info->width ; j++)
		{
				if( !calcQcode(qCode, i, j, image->n) )
					return 0;
				
				if( !insertLeaf(image->qTree, qCode, &pixelVec[j]) )
					return 0;
		}
	}

	free( pixelVec );
	free(qCode);
	
	return 1;
}

static int copyPixel(ElementT pixDest, ElementT pixSource)
{
	SINGLE_PIXEL * dest = pixDest;
	SINGLE_PIXEL * source = pixSource;

	if(dest == NULL || source == NULL)
		return 0;
	
	dest->red = source->red;
	dest->green = source->green;
	dest->blue = source->blue;
	return 1;
}

static void freePixel(ElementT pixel)
{
	SINGLE_PIXEL * p = pixel;
	
	if( p == NULL )
		return;
	
	free(p);
		
	return;
}

/*
 * fixSize escribira el height y el width a usar por el quadTree.
 * La imagen tiene que ser cuadrada y de la forma 2^n x 2^n.
 * Primero se fijara si height y width son potencias de 2.
 * Mejor caso:
 * - Si height y width son potencias de dos y son iguales, se usan esos.
 * Uno de los dos no es potencia de 2:
 * - Se vera cual es mayor y se buscara una potencia de 2 mayor a los dos. 
*/

static void fixSize(imageADT image)
{
	unsigned long h, w;
	
	h = isPower2(image->info->height);
	w = isPower2(image->info->width);

	if( h == w && h != 0 )
	{
		image->sizeFixed = h;
		return;
	}

	if( image->info->height < image->info->width )
		image->sizeFixed = seekPower2(image->info->width);
	else
		image->sizeFixed = seekPower2(image->info->height);
	
	return;
}
static int seekN(int x)
{
	unsigned long pow2 = 2;
	int i = 1;

	if( x == 1 ) /*Caso especial*/
		return 0;

	if( !isPower2(x) )
		return -1;

	while( pow2 < x )
	{
		pow2 *= 2;
		i++;
	}

	return i;
}

static unsigned long seekPower2(unsigned long x)
{
	unsigned long pow2 = 2;
	
	while( pow2 < x )
		pow2 *= 2;

	return pow2;
}

static unsigned long isPower2(unsigned long x)
{
	unsigned long var = x;
	
	if( x <= 0 )
		return 0;

	while( !((x&1) == 1) )
		x >>=1;

	if( x == 1 )
		return var;
	else
		return 0;
}
int saveImage(imageADT image, char * file)
{
	FILE * dest;

	if( image == NULL || file == NULL)
		return 0;

	if( (dest = fopen( file, "wr")) == NULL )
		return 0;

	if( !writeFirstHeader(image, dest) )
		return 0; 

	if( !writeSecondHeader(image, dest) )
		return 0;

	if( !writeImage(image, dest) )
		return 0;

	fclose(dest);

	return 1;
}

/*
 * Esta funcion recorre el cuadrado del quadTree viendo
 * si los pixeles existen o no.
 * Sin importar la transformacion que se le hizo a la
 * imagen, va a dejarla bien.
 * El flag se usa para que se escriban los bytes para que la 
 * fila sea multi de cuatro siempre y cuando los pixeles que
 *  se hayan leido en esa linea sean != NULL.
*/

static int writeImage(imageADT image, FILE * dest)
{
	SINGLE_PIXEL * pixel;
	int i, j, k;
	char * qCode;
	unsigned char garbage = 'g';
	int mod4;
	int flag;
	
	mod4 = seekMod4(image->info->width * 3);
	
	if( (qCode = malloc(image->n + 1)) == NULL )
		return 0; 
		
	for( i=0 ; i < image->sizeFixed ; i++ )
	{
		for( j=0, flag=0 ; j < image->sizeFixed ; j++)
		{
			if( !calcQcode(qCode, i, j, image->n) )
				return 0;

			if( (pixel = getElement( image -> qTree, qCode)) != NULL )
			{
				flag = 1;
				writePixel(pixel, dest);
			}
		}
		if(flag)
		{
			for( k= 0 ; k < mod4 ; k++ )
				fwrite(&garbage, sizeof(unsigned char), 1, dest);
		}
	}

	free(qCode);
	return 1;
}

static int seekMod4(unsigned long var)
{
	int resp = 4 - var%4;
	
	if( resp == 4 )
		return 0;

	return resp; 

}

static void writePixel(SINGLE_PIXEL * pixel, FILE * dest)
{
	fwrite(&pixel->blue, sizeof(unsigned char), 1, dest);
	fwrite(&pixel->green, sizeof(unsigned char), 1, dest);
	fwrite(&pixel->red, sizeof(unsigned char), 1, dest);
	
	return;
}

static int writeFirstHeader(imageADT image, FILE * dest)
{
	if( image == NULL || dest == NULL)
		return 0;
	
	if( (fwrite(&image->header->type, sizeof(unsigned short), 1, dest)) == 0 )
		return 0;

	if( (fwrite( &image->header->size, sizeof(unsigned long), 1, dest)) == 0 )
		return 0;

	if( (fwrite( &image->header->reserved1, sizeof(unsigned short), 1, dest)) == 0 )
		return 0;
		
	if( (fwrite( &image->header->reserved2, sizeof(unsigned short), 1, dest)) == 0 )
		return 0;
	
	if( (fwrite( &image->header->offsetbits, sizeof(unsigned long), 1, dest)) == 0 )
		return 0;

	return 1;
}

static int readFirstHeader(imageADT image, FILE * source)
{

	if( image == NULL || source == NULL)
		return 0;
	
	if( (image->header = malloc(sizeof(BITMAPFILEHEADER))) == NULL )
		return 0;

	if( (fread( &image->header->type, sizeof(unsigned short), 1, source )) == 0 )
		return 0;
		
	if( (fread( &image->header->size, sizeof(unsigned long), 1, source )) == 0 )
		return 0;
		
	if( (fread( &image->header->reserved1, sizeof(unsigned short), 1, source )) == 0 )
		return 0;
		
	if( (fread( &image->header->reserved2, sizeof(unsigned short), 1, source )) == 0 )
		return 0;
		
	if( (fread( &image->header->offsetbits, sizeof(unsigned long), 1, source )) == 0 )
		return 0;

	return 1;
}
static int writeSecondHeader(imageADT image, FILE * dest)
{
	if( image == NULL || dest == NULL)
		return 0;

	if( (fwrite( &image->info->size, sizeof(unsigned long), 1, dest )) == 0 )
		return 0;
		
	if( (fwrite( &image->info->width, sizeof(unsigned long), 1, dest )) == 0 )
		return 0;
		
	if( (fwrite( &image->info->height, sizeof(unsigned long), 1, dest )) == 0 )
		return 0;
		
	if( (fwrite( &image->info->planes, sizeof(unsigned short), 1, dest )) == 0 )
		return 0;
		
	if( (fwrite( &image->info->bitcount, sizeof(unsigned short), 1, dest )) == 0 )
		return 0;
		
	if( (fwrite( &image->info->compression, sizeof(unsigned long), 1, dest )) == 0 )
		return 0;
		
	if( (fwrite( &image->info->sizeimage, sizeof(unsigned long), 1, dest )) == 0 )
		return 0;
		
	if( (fwrite( &image->info->xpelspermeter, sizeof(long), 1, dest )) == 0 )
		return 0;
		
	if( (fwrite( &image->info->ypelspermeter, sizeof(long), 1, dest )) == 0 )
		return 0;
		
	if( (fwrite( &image->info->colorsused, sizeof(unsigned long), 1, dest )) == 0 )
		return 0;
		
	if( (fwrite( &image->info->colorsimportant, sizeof(unsigned long), 1, dest )) == 0 )
		return 0;

	return 1;
}

static int readSecondHeader(imageADT image, FILE * source)
{
	if( image == NULL || source == NULL)
		return 0;

	if( (image->info = malloc(sizeof(BITMAPINFOHEADER))) == NULL )
		return 0;

	if( (fread( &image->info->size, sizeof(unsigned long), 1, source )) == 0 )
		return 0;
		
	if( (fread( &image->info->width, sizeof(unsigned long), 1, source )) == 0 )
		return 0;
		
	if( (fread( &image->info->height, sizeof(unsigned long), 1, source )) == 0 )
		return 0;
		
	if( (fread( &image->info->planes, sizeof(unsigned short), 1, source )) == 0 )
		return 0;
		
	if( (fread( &image->info->bitcount, sizeof(unsigned short), 1, source )) == 0 )
		return 0;
		
	if( (fread( &image->info->compression, sizeof(unsigned long), 1, source )) == 0 )
		return 0;
		
	if( (fread( &image->info->sizeimage, sizeof(unsigned long), 1, source )) == 0 )
		return 0;
		
	if( (fread( &image->info->xpelspermeter, sizeof(long), 1, source )) == 0 )
		return 0;
		
	if( (fread( &image->info->ypelspermeter, sizeof(long), 1, source )) == 0 )
		return 0;
		
	if( (fread( &image->info->colorsused, sizeof(unsigned long), 1, source )) == 0 )
		return 0;
		
	if( (fread( &image->info->colorsimportant, sizeof(unsigned long), 1, source )) == 0 )
		return 0;

	return 1;
}

static void
printColours(void * colour)
{
	SINGLE_PIXEL * auxi;
	auxi = colour;
	
	printf("rgb(%3d, %3d, %3d): ", auxi -> red, auxi -> green, auxi -> blue);
	return;
}

static int
hash(void * element, int interval)
{
	int c2=0x27d4eb2d; /* un primo o constante impar */
 	SINGLE_PIXEL * auxi;
 	int key;
 	
 	if (element == NULL || interval <= 0)
 		return -1;
 	
 	auxi = element;
 	key = auxi -> red *  1019
 	+ auxi -> blue * 4019
 	+ auxi -> green * 6451;
 
	key = (key ^ 61) ^ (key >> 16);
	key = key + (key << 3);
	key = key ^ (key >> 4);
	key = key * c2;
	key = key ^ (key >> 15);
	return key % interval;
}

int printHeader(imageADT image)
{
	if( image == NULL )
		return 0;

	if(image->header == NULL )
		return 0;
		
	printf("Type: %d\n", image->header->type);
	printf("Size: %ld\n", image->header->size);
	printf("Reserved1: %d\n", image->header->reserved1);	
	printf("Reserved2: %d\n", image->header->reserved2);
	printf("Offsetbits: %ld\n", image->header->offsetbits);

	return 1;
}

int printInfo(imageADT image)
{
	if( image == NULL )
		return 0;

	if(image->header == NULL )
		return 0;
			
	printf("Size: %ld\n", image->info->size);
	printf("Width: %ld\n", image->info->width);
	printf("Height: %ld\n", image->info->height);
	printf("Planes: %d\n", image->info->planes);
	printf("Bitcount: %d\n", image->info->bitcount);
	printf("Compression: %ld\n", image->info->compression);
	printf("Sizeimage: %ld\n", image->info->sizeimage);
	printf("Xpelspermeter: %ld\n", image->info->xpelspermeter);
	printf("Ypelspermeter: %ld\n", image->info->ypelspermeter);
	printf("Colorused: %ld\n", image->info->colorsused);
	printf("Colorsimportant: %ld\n", image->info->colorsimportant);

	return 1;
}

static int calcQcode( char * code, int fil, int col, int n )
{
	char filN[n + 1],  colN[n + 1];
	int dimFil, dimCol, dimMax;

	if( fil < 0 || col < 0 || n < 1 )
		return 0;

	sprintf(filN, "%0.f", toBin( fil ) );
	sprintf(colN, "%0.f", toBin( col ) );
	
/*
 * Guarda las dimensiones de fil y cols para indexar.
 * Se decrementa al final del bloque porque se van a usar para indexar. 
*/
	dimFil = strlen( filN );
	dimCol = strlen( colN );
	dimMax = n;

	dimFil--;
	dimCol--;
	
	code[dimMax--] = 0; /*Se marca el final del string code*/

/*
 * Mientras los dos tengan numeros, se analiza para ver que guardar
 * en el string code segun:
 * Fil - col => x
 * 0 - 0 => 0
 * 0 - 1 => 1
 * 1 - 0 => 2
 * 1 - 1 => 3
 * */
	
	while( dimFil >= 0 && dimCol >= 0 )
	{
		code[dimMax]=filN[dimFil]=='1'?(colN[dimCol]=='1'?'3':'2'):(colN[dimCol]=='1'?'1':'0');
		dimMax--;
		dimCol--;
		dimFil--;
	}

/*
 * Si me quedan de uno, y de otro no, se supone que tiene ceros.
 * Ejemplo:
 * fil: 110
 * col:  01
 * code: 221
 */
 
	while( dimFil >= 0 )
	{
		code[dimMax--]= filN[dimFil] == '1' ? '2' : '0';
		dimFil--;
	}
	
	while( dimCol >= 0 )
	{
		code[dimMax--]= colN[dimCol] == '1' ? '1' : '0';
		dimCol--;
	}
	
	while( dimMax >= 0 )
		code[dimMax--]= '0';

	return 1;
}
	
	
static double toBin( int num )
{
	double aux = 0;
	int i = 0;
	
	while( num >= 2 )
	{
		aux += (num%2)*pow(10,i++);
		num /= 2;
	}
	
	aux += num*pow(10,i);
	
	return aux;
}

int closeImage(imageADT image)
{
	free(image->header);
	free(image->info);
	
	if( image != NULL )
		freeQtree(image->qTree);
	
	free(image);

	return 1;
}

static int cmpElement(ElementT color1, ElementT color2)
{
	SINGLE_PIXEL * auxColor1 = color1;
	SINGLE_PIXEL * auxColor2 = color2;
	
	if( auxColor1 == NULL || auxColor2 == NULL )
		return 0;
		
	else if (auxColor1->red != auxColor2->red)
		return 0;
		
	else if (auxColor1->green != auxColor2->green)
		return 0;
	
	else if (auxColor1->blue != auxColor2->blue)
		return 0;
		
	return 1; 	
}

int mirror(imageADT image, int orientation)
{
	pole coord1, coord2;
	
	if (orientation)
	{
		coord1 = E;
		coord2 = O;
	}
	else
	{
		coord1 = N;
		coord2 = S;
	}
	
	if (!swapQPoles(image -> qTree, coord1, coord2))
		return 0;
		
	return 1;
}


int intersection(imageADT image1, imageADT image2, unsigned char red, unsigned char green, unsigned char blue)
{
	if( image1 == NULL || image2 == NULL )
		return 0;
	
	if( image1->info->height != image2->info->height		||
		image1->info->width != image2->info->width			||
		image1->info->bitcount != image2->info->bitcount	||
		image1->sizeFixed != image2->sizeFixed				)

		return 0;
	
	quadADT qTreeNew;
	if( (qTreeNew = NewQuad(sizeof(SINGLE_PIXEL), freePixel, copyPixel, cmpElement)) == NULL)
		return 0;

	int i, j;
	SINGLE_PIXEL * pixel1, * pixel2, * pMix;
	char * qCode;

	if( (pMix = malloc(sizeof(SINGLE_PIXEL))) == NULL )
		return 0;
		
	pMix->blue = blue;
	pMix->green = green;
	pMix->red = red;
	
	if( (qCode = malloc(image1->n + 1)) == NULL )
		return 0; 		

	for( i=0 ; i < image1->sizeFixed ; i++ )
	{
		for( j=0 ; j < image1->sizeFixed ; j++)
		{
			if( !calcQcode(qCode, i, j, image1->n ) )
				return 0;/*Error al mallocar especio para el string.*/

			pixel1 = getElement( image1->qTree, qCode);
			pixel2 = getElement( image2->qTree, qCode);
	
			
			if( cmpElement(pixel1, pixel2) )
			{
				if( !insertLeaf(qTreeNew, qCode, pixel1) )
					return 0;
			}
			else if( pixel1 != NULL && pixel2 != NULL )
			{
				if( !insertLeaf(qTreeNew, qCode, pMix) )
					return 0;
			}

		}
	}
	
	free(qCode);
	freeQtree(image1->qTree);
	image1->qTree = qTreeNew;
	free( pMix );
	

	return 1;
}


int rotation( imageADT image, int orientation )
{
	unsigned long int aux;
	
/* Chequea que la orientacion elegida sea la correcta */
	
	if (orientation != 1 && orientation != 0)
		return 0;
	
	rotateQPoles( image->qTree, orientation);
	
	/*si la imagen no era cuadrada, tengo que cambiar el ancho por largo y viceversa
	 * para cuando la voy a guardar no estar levantando mal*/
	 
	aux = image->info->height;
	image->info->height = image->info->width;
	image->info->width = aux;
	
	return 1;
}

int composition(imageADT image)
{
	hashADT hashtable;
	int dim;
	const void * colours;
		
	hashtable = newHash(cmpElement, hash);
	hashLeafs(hashtable, image -> qTree, image -> sizeFixed * image -> sizeFixed);
	
	colours = quantitySort(hashtable, &dim);
	
	printf("Ancho: %3ld \nAlto: %3ld \n \n", image -> info -> width, image -> info -> height); 
	
	printSorted(colours, dim, printColours, image -> info -> width * image -> info -> height);
	freeHash(hashtable);
	free((void *)colours);
	return 1;
}
	

/*
 * headerCheck verifica que la imagen sea valida.
 * Por ejemplo si es una imagen de 24 bits
*/
int headerCheck(imageADT image)
{
	if( image == NULL )
		return 0;
	
	if( image->info == NULL || image->header == NULL )
		return 0;
	
	if( image->header->offsetbits != 54	||
		image->header->size <= 0		||
		image->header->type != _BM_		||

		image->info->bitcount != 24		||
		image->info->compression != 0	||
		image->info->height <= 0		||
		image->info->width <= 0			||
		image->info->size != 40			)
	
		return 0;

	return 1;
}
